package net.inttes.lotura.binding.form.support;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.inttes.lotura.binding.convert.ConversionService;
import net.inttes.lotura.binding.convert.support.DefaultConversionService;
import net.inttes.lotura.binding.form.CommitListener;
import net.inttes.lotura.binding.form.FieldMetadata;
import net.inttes.lotura.binding.form.FormModel;
import net.inttes.lotura.binding.value.CommitTrigger;
import net.inttes.lotura.binding.value.ValueModel;
import net.inttes.lotura.binding.value.support.AbstractPropertyChangePublisher;
import net.inttes.lotura.binding.value.support.BufferedValueModel;
import net.inttes.lotura.binding.value.support.MapKeyAdapter;
import net.inttes.lotura.binding.value.support.TypeConverter;
import net.inttes.lotura.binding.value.support.ValueHolder;
import net.inttes.lotura.core.closure.Closure;

import com.google.gwt.core.client.GWT;


public class DefaultFormModel extends AbstractPropertyChangePublisher implements FormModel {
    
	private String id;
	private ValueModel formObjectHolder;
	private boolean buffered = false;
	private boolean enabled = true;
	private ConversionService conversionService;
	private CommitTrigger commitTrigger = new CommitTrigger();
	private final Map propertyValueModels = new HashMap();
	private final Map convertingValueModels = new HashMap();
	private final Map fieldMetadata = new HashMap();
	private final List commitListeners = new ArrayList();
		

    public DefaultFormModel(Object domainObject) {
        this(new ValueHolder(domainObject), true);
    }

    
    public DefaultFormModel(Object domainObject, boolean buffered) {
        this(new ValueHolder(domainObject), buffered);
    }

    
    protected DefaultFormModel(ValueModel formObjectHolder, boolean buffered) {
//        prepareValueModel(formObjectHolder);
        this.formObjectHolder = formObjectHolder;
        this.buffered = buffered;        
    }



    public String getId() {
        return id;
    }

    
    public void setId(String id) {
        this.id = id;
    }

    
    public Object getFormObject() {
        return getFormObjectHolder().getValue();
    }

    
    public void setFormObject(Object formObject) {
//  	setDeliverValueChangeEvents(false);
    	if (formObject == null) {
    		throw new IllegalArgumentException("FormObject cannot be null");
    	}
    	
    	getFormObjectHolder().setValue(formObject);
    	setEnabled(true);

    	// this will cause all buffered value models to revert 
    	// to the new form objects property values 
    	commitTrigger.revert();
//  	setDeliverValueChangeEvents(true);
    }


    public ValueModel getFormObjectHolder() {
        return formObjectHolder;
    }


    public boolean hasValueModel(String formProperty) {
        return propertyValueModels.containsKey(formProperty);
    }

    
    public ValueModel getValueModel(String formProperty) {
        ValueModel propertyValueModel = (ValueModel) propertyValueModels.get(formProperty);
        if (propertyValueModel == null) {
            propertyValueModel = buffered ? new BufferedValueModel(createValueModel(formProperty), commitTrigger) 
            								: createValueModel(formProperty);
            propertyValueModels.put(formProperty, propertyValueModel);
        }
        return propertyValueModel;
    }
    
    
    public ValueModel getValueModel(String formProperty, String targetClass) {
    	String key = formProperty + "-" + targetClass;
		ValueModel convertingValueModel = (ValueModel) convertingValueModels.get(key);
		if (convertingValueModel == null) {
			convertingValueModel = createConvertingValueModel(formProperty, targetClass);
			convertingValueModels.put(key, convertingValueModel);
		}
		return convertingValueModel;
    }
    
    
    public FieldMetadata getFieldMetadata(String field) {
    	return (FieldMetadata) fieldMetadata.get(field);
    }
    
    
    public void commit() {
        GWT.log("[DefaultFormModel] Commit requested for this form model " + this, null);
        
        if (getFormObject() == null) {
        	GWT.log("[DefaultFormModel] Form object is null; nothing to commit.", null);
            return;
        }
        
        if (isCommittable()) {
            for (Iterator i = commitListeners.iterator(); i.hasNext();) {
                ((CommitListener) i.next()).preCommit(this);
            }
            preCommit();
            if (isCommittable()) {
            	commitTrigger.commit();   //doCommit();
                postCommit();
                for (Iterator i = commitListeners.iterator(); i.hasNext();) {
                    ((CommitListener) i.next()).postCommit(this);
                }
            }
            else {
                throw new IllegalStateException("Form model '" + this
                        + "' became non-committable after preCommit phase");
            }
        }
        else {
            throw new IllegalStateException("Form model '" + this + "' is not committable");
        }
    }
    
    
    public void revert() {
        commitTrigger.revert();
    }

    
    public void reset() {
    	// TODO
        //setFormObject(null);
    }


    public boolean isBuffered() {
        return buffered;
    }

    
    public boolean isDirty() {
    	throw new UnsupportedOperationException();
    }
    

    public void setEnabled(boolean enabled) {
    	if (!hasChanged(this.enabled, enabled)) {
    		return;
    	}
        this.enabled = enabled;
        firePropertyChange(ENABLED_PROPERTY, !enabled, enabled);
//        enabledUpdated();
    }
    

    public boolean isEnabled() {
        return enabled;
    }

    
    public boolean isCommittable() {
    	return true;
    }    
    

    public void addCommitListener(CommitListener listener) {
        commitListeners.add(listener);
    }

    
    public void removeCommitListener(CommitListener listener) {
        commitListeners.remove(listener);
    }

    
    public String toString() {
    	return GWT.getTypeName(this) + ':' + id;
    }
    
    
    public ConversionService getConversionService() {
    	if (conversionService == null) {
    		conversionService = new DefaultConversionService();
    	}
    	return conversionService;
    }
    

    public void setConversionService(ConversionService conversionService) {
    	this.conversionService = conversionService;
    }

    //----------------------------------------------------------
    
    protected ValueModel createValueModel(String formProperty) {
    	return new MapKeyAdapter(getFormObjectHolder(), formProperty);
    	
    	// TODO delegar usando PopertyAccessStrategy
//        return buffered ? new BufferedValueModel(propertyAccessStrategy.getPropertyValueModel(formProperty))
//        : propertyAccessStrategy.getPropertyValueModel(formProperty);    	
    }

    
    protected ValueModel createConvertingValueModel(String formProperty, String targetClass) {
    	GWT.log("Creating converting value model for form property '" + formProperty
                + "' converting to type '" + targetClass + "'.", null);
    	
    	final ValueModel sourceValueModel = getValueModel(formProperty);
    	final String sourceClass = getFieldMetadata(formProperty).getPropertyType();
        if (sourceClass.equals(targetClass)) {
            return sourceValueModel;
        }
        
        Closure convertTo = getConversionService().getConversionClosure(sourceClass, targetClass);
        Closure convertFrom = getConversionService().getConversionClosure(targetClass, sourceClass);
        
        ValueModel convertingValueModel = new TypeConverter(sourceValueModel, convertTo, convertFrom);
        return convertingValueModel;
    }
    
    
    protected void preCommit() {
    }


    protected void postCommit() {
    }

}
